package biback.domain.product

import biback.domain._
import ProductStatus._
import biback.domain.account.OneCertificate
import biback.domain.common.model.Currency.CNY
import biback.domain.common.model.{ChannelType, Currency}
import biback.domain.customer.{CustomerStatus, IdValidationType}
import biback.domain.product.Limits.{LimitKey, LimitType}
import biback.utils.DomainObject

case class ProductParam
(
  status: ProductStatus = Opened,
  openChannels: List[ChannelType] = List.empty[ChannelType],
  openCurrencies: List[Currency] = List(CNY),
  openBranches: List[BranchNo] = List.empty[BranchNo],
  cashing: Boolean = false,
  cashingChannels: List[ChannelType] = List.empty[ChannelType],
  minMaxLimits: Map[LimitType, NnDec] = Map.empty[LimitType, NnDec],
  idValidatedRequires: Map[ChannelType, IdValidationType] = Map.empty[ChannelType, IdValidationType]
)  extends DomainObject {
  import ProductParam._
  val statusX = (i: ProductStatus) => i == status ?= InvalidProductStatus(status)
  val openChannelX = (i: OpenChannelSetter) => openChannels.contains(i.openChannel) ?= InvalidOpenChannel(i.openChannel)
  val openCurrencyX = (i: OpenCurrencySetter) => openCurrencies.contains(i.openCurrency) ?= InvalidOpenCurrency(i.openCurrency)
  val openBranchX = (i: OpenBranchSetter) => openBranches.contains(i.openBranch) || openBranches.isEmpty ?= InvalidOpenBranch(i.openBranch)
  val certCheckX = (i: OpenChannelSetter with CertSetter) => i.openChannel != ChannelType.Term || i.cert.nonEmpty ?= CertBoundRequired
  val preOpenBaseX = (inputs: BasePreOpenSetter) =>
    for {
      _ <- statusX(ProductStatus.Opened)
      _ <- openChannelX(inputs)
      _ <- openCurrencyX(inputs)
      _ <- openBranchX(inputs)
    } yield ()
  val customerStatusX = (i: CustomerStatusSetter, s: CustomerStatus) => i.customerStatus == s ?= InvalidCustomerStatus(i.customerStatus)
  val minMaxLimitsX = (i: MinMaxValuesSetter) =>
    i.minMaxValues.foldLeft(NONE)(minMaxDecide)
  private def minMaxDecide(out: ErrOrNone, kv: (LimitKey, BiDec)): ErrOrNone = {
    val (k, v) = (kv._1.limit, kv._2)
    val minMax = minMaxLimits.get(k)
    for {
      _ <- out
      m <- minMax ?= MinMaxLimitNotFound(k)
      _ <- (k.isMax && v <= m.value) || (!k.isMax && v >= m.value) ?= MinMaxLimitViolated(kv._1, m, kv._2)
    } yield ()
  }
  val idValidatedCheck = (p: IdValidationType, r: IdValidationType) =>
    p == r ?= NotSatisfiedIdValidated(p, r)
  val idValidatedX = (i: OpenChannelSetter with IdValidatedSetter) =>
    idValidatedRequires.get(i.openChannel).map(idValidatedCheck(i.idValidated, _)).getOrElse(NONE)
  val cashingX = (i: TrnChannelSetter with InvokerSetter) =>
    for {
      _ <- cashing ?= NotCashingProduct
      _ <- cashingChannels.contains(i.trnChannel) ?= InvalidCashingChannel(i.trnChannel)
      _ <- i.invoker ?= EmptyInvoker
    } yield ()
}

object ProductParam {
  trait OpenChannelSetter {
    val openChannel: ChannelType
  }

  trait TrnChannelSetter {
    val trnChannel: ChannelType
  }

  trait OpenCurrencySetter {
    val openCurrency: Currency
  }

  trait OpenBranchSetter {
    val openBranch: BranchNo
  }

  trait CertSetter {
    val cert: Option[OneCertificate]
  }

  trait IdValidatedSetter {
    val idValidated: IdValidationType
  }

  trait CustomerStatusSetter {
    val customerStatus: CustomerStatus
  }

  trait MinMaxValuesSetter {
    val minMaxValues: Map[LimitKey, BiDec]
  }

  trait InvokerSetter {
    val invoker: Option[TellerNo]
  }

  type BasePreOpenSetter = OpenChannelSetter with OpenCurrencySetter with OpenBranchSetter
}
